package com.xyzwps.mvckotlin.handler.helper

import com.xyzwps.mvckotlin.config.Const
import com.xyzwps.mvckotlin.exception.BizException
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.reactor.mono
import kotlinx.coroutines.slf4j.MDCContext
import org.slf4j.LoggerFactory
import org.slf4j.MDC
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.ServerResponse.ok
import org.springframework.web.reactive.function.server.ServerResponse.status
import reactor.core.publisher.Mono

import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR

internal val log = LoggerFactory.getLogger("com.xyzwps.mvckotlin.handler.wrapper")

/**
 * 这个 mdc 用在发起一个 coroutine 之前
 */
fun mdc(request: ServerRequest) = MDC.put("xid", request.headers().header(Const.X_REQ_ID).first())

internal object ErrorWrapper {
    fun bizerror(e: BizException) = mapOf("message" to e.message, "class" to e::class.java.name, "cause" to e.cause?.message, "code" to e.code.name)
    fun throwable(e: Throwable) = mapOf("message" to e.message, "class" to e::class.java.name, "cause" to e.cause?.message)
}


/**
 * 请求返回 json 格式数据
 *
 * 如果你不使用 request body 的话（如 GET 请求），使用此函数
 */
fun <R> json(handler: JsonHandler<R>): RequestHandler = fun(request: ServerRequest): Mono<ServerResponse> {
    mdc(request)
    return GlobalScope.mono(MDCContext()) {
        handler(request)
    }.flatMap {
        ok().json().bodyFromObject(it)
    }.onErrorResume(BizException::class.java) {
        status(it.httpStatus).json().bodyFromObject(ErrorWrapper.bizerror(it))
    }.onErrorResume(Throwable::class.java) {
        status(INTERNAL_SERVER_ERROR).json().bodyFromObject(ErrorWrapper.throwable(it))
    }
}

fun render(handler: MavHandler): RequestHandler = fun(request: ServerRequest): Mono<ServerResponse> {
    mdc(request)
    return GlobalScope.mono(MDCContext()) {
        handler(request)
    }.flatMap { mav ->
        // TODO: kotlin 是如何判断应该调用哪个函数的？
        // data class ModelAndView(val name: String, val model: Map<String, Any?> = mapOf())
        // data class ModelAndView(val name: String, val model: Map<String, Any?>? = mapOf())
        ok().render(mav.name, mav.model)
    }.onErrorResume(BizException::class.java) { e ->
        log.error(e.message, e)
        status(e.httpStatus).render("error/bizerror", ErrorWrapper.bizerror(e))
    }.onErrorResume(Throwable::class.java) { e ->
        log.error(e.message, e)
        status(INTERNAL_SERVER_ERROR).render("error/bizerror", ErrorWrapper.throwable(e))
    }
}
